#pragma once

#include "TransportLayer.h"
#include "MessageHeader.h"
#include "Channel.h"
#include "BitStream.h"
#include <algorithm>

namespace phynet{
namespace net{

struct SameAddress
{
	explicit SameAddress(const Address& i_dest)
	{
		m_addr = &i_dest;
	}

	bool operator()(const Connection& i_conn)
	{
		return i_conn.GetAddr() == *m_addr;
	}

private:
	const Address* m_addr;
};

TransportLayer::TransportLayer()
	:m_status(StatusClosed)
{
}

TransportLayer::~TransportLayer()
{
	Close(true);
}

bool TransportLayer::Open(int i_port)
{
	if(m_status == StatusClosing)
		return false;

	//if the socket is already open, we don't close it and reopen
	//so check the port and eventually do it by yourself

	m_status = StatusOpen;

	return m_socket.Open(i_port);
}

void TransportLayer::Close(bool i_hardDisconnect)
{
	if(i_hardDisconnect || m_connections.empty())
	{
		m_socket.Close();
		m_connections.clear();
	}
	else
	{
		if(m_status != StatusClosing)
			BroadcastPacket(Reliable, DisconnectionMessageType, 0);
	}
}

bool TransportLayer::SendPacket(const Address& i_dest, int i_channelID, int i_type, const BitStream* i_data)
{
	if(m_status != StatusOpen)
		return false;

	//find if the address is present inside connections
	Connections::iterator it = 
		std::find_if(m_connections.begin(), m_connections.end(), SameAddress(i_dest));

	if(it == m_connections.end())
		return false;

	MessageHeader header;
	header.size = sizeof(MessageHeader) + (i_data ? i_data->GetByteSize() : 0);
	header.type = i_type;
	header.channel = i_channelID;

	it->GetChannel(i_channelID).MessageSend(header, i_data);
	
	return true;
}

bool TransportLayer::BroadcastPacket(int i_channelID, int i_type, const BitStream* i_data)
{
	if(m_status != StatusOpen)
		return false;

	for(size_t i = 0; i < m_connections.size(); ++i)
		SendPacket(m_connections[i].GetAddr(), i_channelID, i_type, i_data);

	return true;
}

void TransportLayer::Update(MessageInfos& o_infos)
{
	if(m_status == StatusClosed)
		return;

	//Reading if OPEN or CLOSING
	//maximum NUM_READINGS reading (performance reasons)
	int numReadings = NUM_READINGS;
	bool readSomething = true;
	while((numReadings > 0) && (readSomething == true))
	{
		Address addr;
		char message[1024];
		UInt messageSize = 1024;
		readSomething = m_socket.RecvFrom(addr, message, messageSize);

		HandleIncomingMessage(o_infos, addr, message, messageSize);

		--numReadings;
	}

	if(m_status != StatusOpen)
		return;

	//Sending only if OPEN
	//maximum NUM_SENDINGS sending (performance reasons)
	FlushMessages();
}

void TransportLayer::HandleIncomingMessage(MessageInfos& o_infos, const Address& i_addr, const char* i_message, const UInt i_messageSize)
{
	BitStream message(reinterpret_cast<const Byte*>(i_message), i_messageSize);
	MessageHeader header;
	message.ReadType(header);

	switch(header.type)
	{
	case DisconnectionMessageType:
		{
			Connections::iterator it = 
				std::find_if(m_connections.begin(), m_connections.end(), SameAddress(i_addr));
			
			if(it == m_connections.end())
				return;

			m_connections.erase(it);
		}
		break;

	case UserMessageType:
		MessageInfo info;
		info.m_type = UserMessageType;
		info.m_data = new BitStream;
		info.m_data->WriteBytes(reinterpret_cast<const Byte*>(i_message) + sizeof(MessageHeader), i_messageSize - sizeof(MessageHeader));
		info.m_sender = i_addr;
		o_infos.push_back(info);
		break;

	default:
		break;
	}
	//TODO
}

}} //namespaces